1   // Copyright 2007, 2008, 2009, 2010 The Apache Software Foundation
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   // http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  
15  package org.apache.tapestry5.internal.services;
16  
17  import java.math.BigDecimal;
18  import java.math.BigInteger;
19  import java.text.DecimalFormatSymbols;
20  import java.text.ParseException;
21  import java.util.Locale;
22  import java.util.Map;
23  
24  import org.apache.tapestry5.Field;
25  import org.apache.tapestry5.Translator;
26  import org.apache.tapestry5.ValidationException;
27  import org.apache.tapestry5.internal.test.InternalBaseTestCase;
28  import org.apache.tapestry5.internal.translator.BigDecimalNumericFormatter;
29  import org.apache.tapestry5.internal.translator.BigIntegerNumericFormatter;
30  import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
31  import org.apache.tapestry5.ioc.services.ThreadLocale;
32  import org.apache.tapestry5.ioc.util.UnknownValueException;
33  import org.apache.tapestry5.services.TranslatorSource;
34  import org.testng.annotations.BeforeClass;
35  import org.testng.annotations.BeforeMethod;
36  import org.testng.annotations.DataProvider;
37  import org.testng.annotations.Test;
38  
39  @SuppressWarnings("unchecked")
40  public class TranslatorSourceImplTest extends InternalBaseTestCase
41  {
42      private TranslatorSource source;
43  
44      @BeforeClass
45      public void setup()
46      {
47          source = getService(TranslatorSource.class);
48      }
49  
50      @BeforeMethod
51      public void setupThreadLocale()
52      {
53          getService(ThreadLocale.class).setLocale(Locale.ENGLISH);
54      }
55  
56      @Test
57      public void found_translator_by_name()
58      {
59          Translator translator = mockTranslator("mock", String.class);
60  
61          replay();
62  
63          TranslatorSource source = new TranslatorSourceImpl(newConfiguration(String.class, translator));
64  
65          assertSame(source.get("mock"), translator);
66  
67          verify();
68      }
69  
70      private Map<Class, Translator> newConfiguration(Class type, Translator t)
71      {
72          Map<Class, Translator> result = CollectionFactory.newMap();
73          result.put(type, t);
74  
75          return result;
76      }
77  
78      @Test
79      public void key_and_type_mismatch()
80      {
81          Translator t = mockTranslator();
82  
83          train_getType(t, Long.class);
84  
85          replay();
86  
87          try
88          {
89              new TranslatorSourceImpl(newConfiguration(Integer.class, t));
90              unreachable();
91          }
92          catch (RuntimeException ex)
93          {
94              assertMessageContains(ex,
95                      "Contributed translator for type java.lang.Integer reports its type as java.lang.Long.");
96          }
97  
98          verify();
99      }
100 
101     @Test
102     public void name_collision_with_standard_translators()
103     {
104         Translator t1 = mockTranslator("fred", Integer.class);
105         Translator t2 = mockTranslator("fred", Long.class);
106 
107         Map<Class, Translator> configuration = CollectionFactory.newMap();
108         configuration.put(Integer.class, t1);
109         configuration.put(Long.class, t2);
110 
111         replay();
112 
113         try
114         {
115             new TranslatorSourceImpl(configuration);
116             unreachable();
117         }
118         catch (RuntimeException ex)
119         {
120             assertMessageContains(
121                     ex,
122                     "Two different Translators contributed to the TranslatorSource service use the same translator name: 'fred'.",
123                     "Translator names must be unique.");
124         }
125 
126         verify();
127     }
128 
129     @Test
130     public void get_alternate_translator_by_name()
131     {
132         Translator t1 = mockTranslator("fred", Integer.class);
133         Translator t2 = mockTranslator();
134 
135         Map<Class, Translator> configuration = newConfiguration(Integer.class, t1);
136 
137         Map<String, Translator> alternates = CollectionFactory.newMap();
138         alternates.put("barney", t2);
139 
140         replay();
141 
142         TranslatorSource source = new TranslatorSourceImpl(configuration, alternates);
143 
144         assertSame(source.get("barney"), t2);
145 
146         verify();
147     }
148 
149     @Test
150     public void name_collision_between_standard_and_alternate_translator()
151     {
152         Translator t1 = mockTranslator("fred", Integer.class);
153         Translator t2 = mockTranslator();
154 
155         Map<Class, Translator> configuration = newConfiguration(Integer.class, t1);
156 
157         Map<String, Translator> alternates = CollectionFactory.newMap();
158         alternates.put("fred", t2);
159 
160         replay();
161 
162         try
163         {
164             new TranslatorSourceImpl(configuration, alternates);
165             unreachable();
166         }
167         catch (RuntimeException ex)
168         {
169             assertEquals(
170                     ex.getMessage(),
171                     "Translator 'fred' contributed to the TranslatorAlternatesSource service has the same name as a standard Translator contributed to the TranslatorSource service.");
172         }
173 
174         verify();
175     }
176 
177     @Test
178     public void unknown_translator_is_failure()
179     {
180         Translator fred = mockTranslator("fred", String.class);
181         Translator barney = mockTranslator("barney", Long.class);
182 
183         Map<Class, Translator> configuration = CollectionFactory.newMap();
184 
185         configuration.put(String.class, fred);
186         configuration.put(Long.class, barney);
187 
188         replay();
189 
190         TranslatorSource source = new TranslatorSourceImpl(configuration);
191 
192         try
193         {
194             source.get("wilma");
195             unreachable();
196         }
197         catch (UnknownValueException ex)
198         {
199             assertMessageContains(ex, "Unknown translator type 'wilma'.");
200         }
201     }
202 
203     @DataProvider
204     public Object[][] to_client_data()
205     {
206         return new Object[][]
207         {
208 
209                 { Byte.class, (byte) 65, "65" },
210 
211                 { Integer.class, 997, "997" },
212 
213                 { Long.class, 12345l, "12345" },
214 
215                 // Is this a bug? We seem to be using a JDK- or locale-defined level of precision.
216                 // Maybe translators need room for configuration just like validators, so that
217                 // the correct decimal format string could be specified in the message catalog.
218 
219                 { Double.class, 3.1428571429d, "3.143" },
220 
221                 { String.class, "abcd", "abcd" },
222 
223                 { Short.class, (short) 95, "95" },
224 
225                 { Float.class, (float) -22.7, "-22.7" },
226 
227                 { BigInteger.class, new BigInteger("123456789012345678901234567890"), "123456789012345678901234567890" },
228 
229                 { BigDecimal.class, new BigDecimal("-9876543219876543321987654321.12345123451234512345"),
230                         "-9876543219876543321987654321.12345123451234512345" } };
231     }
232 
233     @Test(dataProvider = "to_client_data")
234     public void to_client(Class type, Object value, String expected)
235     {
236         Translator t = source.getByType(type);
237 
238         String actual = t.toClient(value);
239 
240         assertEquals(actual, expected);
241     }
242 
243     @DataProvider
244     public Object[][] parse_client_success_data()
245     {
246         return new Object[][]
247         {
248 
249                 { Byte.class, " 23 ", (byte) 23 },
250 
251                 { Short.class, " -121 ", (short) -121 },
252 
253                 { Integer.class, " 123 ", 123 },
254 
255                 { Integer.class, " 20,000 ", 20000 },
256 
257                 { Long.class, "  -1234567 ", -1234567l },
258 
259                 { Double.class, "3.1428571429", 3.1428571429d },
260 
261                 { String.class, " abcdef ", " abcdef " },
262 
263                 { Float.class, " 28.95 ", (float) 28.95 },
264 
265                 { BigInteger.class, " -123456789012345678901234567890",
266                         new BigInteger("-123456789012345678901234567890") },
267 
268                 { BigDecimal.class, "-9,876,543,219,876,543,321,987,654,321.12345123451234512345",
269                         new BigDecimal("-9876543219876543321987654321.12345123451234512345") } };
270     }
271 
272     @Test(dataProvider = "parse_client_success_data")
273     public void parse_client(Class type, String input, Object expected) throws Exception
274     {
275         Translator t = source.getByType(type);
276 
277         Object actual = t.parseClient(null, input, null);
278 
279         assertEquals(actual, expected);
280     }
281 
282     @DataProvider
283     public Object[][] parse_client_failure_data()
284     {
285         String intError = "You must provide an integer value for Fred.";
286         String floatError = "You must provide a numeric value for Fred.";
287 
288         return new Object[][]
289         {
290 
291         { Byte.class, "fred", intError },
292 
293         { Integer.class, "fred", intError },
294 
295         { Long.class, "fred", intError },
296 
297         { Double.class, "fred", floatError },
298 
299         { Float.class, "fred", floatError },
300 
301         { Short.class, "fred", intError } };
302     }
303 
304     @Test(dataProvider = "parse_client_failure_data")
305     public void parse_client_failure(Class type, String input, String expectedMessage)
306     {
307         Translator t = source.getByType(type);
308         Field field = mockField();
309 
310         replay();
311 
312         try
313         {
314             t.parseClient(field, input, expectedMessage);
315             unreachable();
316         }
317         catch (ValidationException ex)
318         {
319             assertEquals(ex.getMessage(), expectedMessage);
320         }
321 
322         verify();
323     }
324 
325     @Test
326     public void find_by_type()
327     {
328         Translator t = mockTranslator("string", String.class);
329 
330         replay();
331 
332         TranslatorSource source = new TranslatorSourceImpl(newConfiguration(String.class, t));
333 
334         assertSame(source.getByType(String.class), t);
335         assertSame(source.findByType(String.class), t);
336         assertNull(source.findByType(Integer.class));
337 
338         verify();
339     }
340 
341     @Test
342     public void get_by_type_not_found()
343     {
344         Translator string = mockTranslator("string", String.class);
345         Translator bool = mockTranslator("bool", Boolean.class);
346 
347         Map<Class, Translator> configuration = CollectionFactory.newMap();
348         configuration.put(String.class, string);
349         configuration.put(Boolean.class, bool);
350 
351         replay();
352 
353         TranslatorSource source = new TranslatorSourceImpl(configuration);
354 
355         try
356         {
357             source.getByType(Integer.class);
358             unreachable();
359         }
360         catch (IllegalArgumentException ex)
361         {
362             assertEquals(ex.getMessage(),
363                     "No translator is defined for type java.lang.Integer.  Registered types: java.lang.Boolean, java.lang.String.");
364         }
365 
366         verify();
367     }
368 
369     @Test
370     public void biginteger_with_localized_symbols() throws ParseException
371     {
372         DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
373         symbols.setGroupingSeparator('_');
374         symbols.setMinusSign('*');
375 
376         BigIntegerNumericFormatter f = new BigIntegerNumericFormatter(symbols);
377 
378         BigInteger big = new BigInteger("-123456");
379 
380         assertEquals(f.parse("*123_456"), big);
381 
382         assertEquals(f.toClient(big), "*123456");
383     }
384 
385     @Test
386     public void bigdecimal_with_localized_symbols() throws ParseException
387     {
388         DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
389         symbols.setGroupingSeparator('_');
390         symbols.setMinusSign('*');
391         symbols.setDecimalSeparator('#');
392 
393         BigDecimalNumericFormatter f = new BigDecimalNumericFormatter(symbols);
394 
395         BigDecimal big = new BigDecimal("-123456.797956563434");
396 
397         assertEquals(f.parse("*123_456#797956563434"), big);
398 
399         assertEquals(f.toClient(big), "*123456#797956563434");
400     }
401 
402 }